//----BEGIN LICENSE----//
/*****************************************************************\
** prevalentLite - sqlite3 persistence object library 
**
** Copyright (C) 2008 Philip R. Schuchardt
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
*****************************************************************/
//----END LICENSE----//
#ifndef plQList_H
#define plQList_H

/*!
  \class plQList plQList.h <plQList>
  \brief Database aware list

  This is a database aware list.  The list can store different any object
  that inherites plData.  The data from the database is cached in std::vector.
  A small subset of std::vector methods are supported

  */

/*---------------------------------------------------------------------
        plQList<T> - Database aware list.  Any changes
        in the list effect the database.  The whole list is queried from
        the database.

        Created by: Philip Schuchardt - 2008-11-06
--------------------------------------------------------------------------*/

//Our includes
#include "plData.h"
#include "plDatabase.h"

//Qt includes
#include <QList>

//STD Vector
#include <vector>
#include <string>
#include <sstream>

//Sqlite includes
#include <sqlite3.h>

template <class T>
        class plQList : public plData {

public:
    //Constructor
    plQList();

    //Descructor
    virtual ~plQList();

    /*!
            \brief Gets data from the index
            \param index - The index of the element
            \return T
        */
    T at(int index);

    /*!
            \brief Replaces a value at an index
            \param index - The index were the value will be replaced
            \param value - The replacement value
         */
    void replace(int index, const T& value);

    /*!
      \brief Remove a value at an index -
        Pointers are not delete, the user most do this.
      \param index - The index where the value will be removed
    */
    void remove(int index);

    /*!
      \brief Inserts a value add an index.
      Add indexes after that will be incromented.
        \param value - The value that will be added
        \param index - The index where the index will be added to
    */
    void insertAt(int index, const T& value);

        /*!
           \brief Adds the value to the end of the list
            \param value - The value to be added to the list
        */
    void push_back(const T& value);

    /*!
            \brief Gets the number of values in the list
            \return The number of entries
        */
    int size();

    /*!
          \brief Gets the index of the item
          \return The index of the item
          */
    int indexOf(const T& value);

    /*!
            \brief Clears all the elements in the array
        */
    void clear();

    /*!
            \brief Loads from database
            Forces the list to load from the database.
            This method can be expensive, because of IO.
          */
    bool LoadFromDB();

    /*!
            \brief Completely Delete the list from the database
        */
    void Delete();

    //Binds the list id with the statement
    virtual void Bind(sqlite3_stmt* statement, int index = 1) const;

    //Gets the list id from the current statement
    virtual void Column(sqlite3_stmt* statement, int index = 0);

    //The classes name
    static const char* GetClassName() { return "plQList"; }

protected:
    bool Cached;
    QList<T>* List;
    long long ListId;
    std::string TableName;

    //Updates the value in the database
    void UpdateDB(int index, const T& value);

    //Removes the value from the database
    void RemoveDB(int index);

    //PushBack the value into the databse
    void PushBackDB(int index, const T& value);

    //Inserts the value into the database
    void InsertDB(int index, const T& value);

    //Clears the database of it's values
    void ClearDB();

    //Creates a list entry in the correct table
    void CreateListDB();

    //Helper to create list DB.  Tries to assign the table a id
    //Return false if can't get an id.  Probably because the table doesn't exist
    bool TryAssignList();

};

//Constructor
template <class T>
        plQList<T>::plQList() {
    List = new QList<T>();
    Cached = false;
    ListId = 0;

    //The table's name
    TableName = T::GetClassName();
    TableName.append("List");

} //end plQList()

//Descructor
template <class T>
        plQList<T>::~plQList() {
    delete List;
}

//Gets data from the index
template <class T>
        T plQList<T>::at(int index) {
    if (!Cached) {
        Cached = LoadFromDB();
    }

    T tmp = List->at(index);
    return tmp;
} // end at

//Replaces a value at an index
template <class T>
        void plQList<T>::replace(int index, const T& value) {

    if (!Cached) {
        Cached = LoadFromDB();
    }

    //We need to make a copy it doesn't complain
    T copyValue;
    copyValue.SetDisabled(true); //Make it retarded
    copyValue = value;

    (*List)[index] = copyValue;

    //Update in the database
    UpdateDB(index, value);

} // end replace

//Removes things a an index
template <class T>
void plQList<T>::remove(int index) {
    if(!Cached) {
        Cached = LoadFromDB();
    }

    List->removeAt(index);

    RemoveDB(index);
}

//Gets the count of the list
template <class T>
        void plQList<T>::push_back(const T& value) {
    if (!Cached) {
        Cached = LoadFromDB();
    }

    int index = List->size();

    //We need to make a copy and disable it so it doesn't complain
    T copyValue;
    copyValue.SetDisabled(true); //Make it retarded
    copyValue = value;

    List->push_back(copyValue);

    //Instert into the databse
    PushBackDB(index, value);
} //end push_back

//Inserts the value at an index
template <class T>
void plQList<T>::insertAt(int index, const T& value) {
    if(!Cached) {
        Cached = LoadFromDB();
    }

    List->insert(index, value);

    InsertDB(index, value);
} //end insertAt

//Gets the number of value in the array
template <class T>
        int plQList<T>::size() {
    if (!Cached) {
        Cached = LoadFromDB();
    }
    return List->size();
} //end count

//Clears all the elements in the array
template <class T>
        void plQList<T>::clear() {
    List->clear();

    //Clear all entries in the database for this index
    ClearDB();
} //end clear

/*!
    \brief Gets the index of the item
    \return The index of the item
*/
template <class T>
        int plQList<T>::indexOf(const T& value) {
    if(!Cached) {
        Cached = LoadFromDB();
    }
    return List->indexOf(value);
}

//Loads from the database - Returns if it's cached or not
template <class T>
        bool plQList<T>::LoadFromDB() {

    List->clear();

    //Need to get the database and the id of this list
    plDatabase* database = GetDatabase();
    database->GetData(this);

    //If we exist as a list
    if(ListId <= 0) {
        CreateListDB();
        return true;
    }

    sqlite3* sqliteDB = database->GetSqliteDB();

    //Get all entries for the list
    std::stringstream sqlStream;
    sqlStream << "SELECT value FROM " << TableName << " WHERE ListId=" << ListId;

    sqlite3_stmt* selectStmt;
    int error = sqlite3_prepare_v2(sqliteDB, sqlStream.str().c_str(), -1, &selectStmt, NULL);

    GetDatabase()->ReadOnly(true);
    //If the prepare statement was okay
    if(error == SQLITE_OK) {
        sqlite3_step(selectStmt); //Skep the dump row
        while(sqlite3_step(selectStmt) == SQLITE_ROW) {
            T value;
            value.SetDisabled(true);
            value.Column(selectStmt, 0);
          //  value.GetData();

            List->push_back(value);
        }
    } //end if(error == SQLITE_OK
    GetDatabase()->ReadOnly(false);

    return true;

} //end LoadFromDB

//Completely Delete the list from the database
template <class T>
        void plQList<T>::Delete() {

    plDatabase* database = GetDatabase();
    sqlite3* sqliteDB = database->GetSqliteDB();

    //Create the sql statement
    std::stringstream sqlStream;
    sqlStream << "DELETE FROM " << TableName << " WHERE ListId=" << ListId;

    int error = sqlite3_exec(sqliteDB, sqlStream.str().c_str(), NULL, NULL, NULL);
    if(error != SQLITE_OK) {
        std::cout << "Error plQList::DeleteDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << std::endl;
    }

    //Update data in the parent's object
    ListId = -1;
    SetData();

    //Clear the list in memory
    List->clear();

    //Make sure we un-cache things
    Cached = false;
}

//Binds the list id with the statement
template <class T>
        void plQList<T>::Bind(sqlite3_stmt* statement, int index) const {
    sqlite3_bind_int64(statement, index, ListId);
}

//Gets the list id from the current statement
template <class T>
        void plQList<T>::Column(sqlite3_stmt* statement, int index ) {
    ListId = sqlite3_column_int64(statement, index);
}

//Updates the value in the database
template <class T>
        void plQList<T>::UpdateDB(int index, const T& value) {
    plDatabase* database = GetDatabase();
    sqlite3* sqliteDB = database->GetSqliteDB();

    //Create the sql statement
    std::stringstream sqlStream;
    sqlStream << "UPDATE  " << TableName << " SET Value=? WHERE ListId=? AND Idx=?";

    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(sqliteDB, sqlStream.str().c_str(), -1, &statement, NULL);
    if(error == SQLITE_OK) {
        //Bind all the statements
        sqlite3_bind_int64(statement, 1, ListId);
        sqlite3_bind_int(statement, 2, index);
        value.Bind(statement, 3);
        sqlite3_step(statement);
    }else {
        std::cout << "Error plQList::UpdateDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << " " << sqlStream.str() << std::endl;
    }
} //end UpdateDB

//Removes the value from the database
template <class T>
void plQList<T>::RemoveDB(int index) {

    plDatabase* database = GetDatabase();
    sqlite3* sqliteDB = database->GetSqliteDB();

    std::stringstream sqlStream;
    sqlStream << "DELETE FROM " << TableName << " where ListId=? AND Idx=? ";

    sqlite3_stmt* statement;
    int error = sqlite3_prepare(sqliteDB, sqlStream.str().c_str(), -1, &statement, NULL);
    if(error == SQLITE_OK) {
        sqlite3_bind_int64(statement, 1, ListId);
        sqlite3_bind_int(statement, 2, index);
        sqlite3_step(statement);
    } else {
        std::cout << "Error plQList::RemoveDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << " " << sqlStream.str() << std::endl;
    }

    //Decrement indexes for

}

//Inserts the value into the databse
template <class T>
        void plQList<T>::PushBackDB(int index, const T& value) {
    plDatabase* database = GetDatabase();
    sqlite3* sqliteDB = database->GetSqliteDB();

    //Create the sql statement
    std::stringstream sqlStream;
    sqlStream << "INSERT INTO " << TableName << " (ListId, Idx, Value) values (?, ?, ?)";

    sqlite3_stmt* statement;
    int error = sqlite3_prepare_v2(sqliteDB, sqlStream.str().c_str(), -1, &statement, NULL);
    if(error == SQLITE_OK) {
        //Bind all the statements
        sqlite3_bind_int64(statement, 1, ListId);
        sqlite3_bind_int(statement, 2, index);
        value.Bind(statement, 3);
        sqlite3_step(statement);
    }else {
        std::cout << "Error plQList::InsertDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << std::endl;
        std::cout << "Error:" << sqlStream.str().c_str() << std::endl;
    }
} //End InsertDB

//Inserts the value into the database
template <class T>
void plQList<T>::InsertDB(int /* index */, const T& /* value */) {
    //Not implemented
} //end insert

//Clears the database of it's values
template <class T>
        void plQList<T>::ClearDB() {
    plDatabase* database = GetDatabase();
    sqlite3* sqliteDB = database->GetSqliteDB();

    //Create the sql statement
    std::stringstream sqlStream;
    sqlStream << "DELETE FROM " << TableName << " WHERE ListId=" << ListId << "AND Index >= 0";

    int error = sqlite3_exec(sqliteDB, sqlStream.str().c_str(), NULL, NULL, NULL);
    if(error != SQLITE_OK) {
        std::cout << "Error plQList::ClearDB() sqlite3: " << sqlite3_errmsg(sqliteDB) << std::endl;
    }
} //End ClearDB


//Creates a list entry in the correct table
template <class T>
        void plQList<T>::CreateListDB() {

    //We can insert the new list into the table
    if(!TryAssignList()) {
        //Try to create a new table and indexs

        plDatabase* database = GetDatabase();
        sqlite3* sqliteDB = database->GetSqliteDB();

        //Create the sql statement
        std::stringstream sqlStream;
        sqlStream << "CREATE TABLE " << TableName << " (ListId INTEGER, Idx INTEGER, Value)";

        int error = sqlite3_exec(sqliteDB, sqlStream.str().c_str(), NULL, NULL, NULL);

        //If creating the table went okay
        if(error == SQLITE_OK) {
            //Create indexes

            /* PREFORMANCE ENHANSEMENT
                        QString indexSQL;
                        QTextStream(&indexSQL) << "CREATE UNIQUE INDEX ListIdIndex ON " << TableName << "(IndexId ASC)";
                        sqlite3_exec(sqliteDB, (const char*)indexSQL.toAscii(), NULL, NULL, NULL);

                        indexSQL.clear();
                        QTextStream(&indexSQL) << "CREATE INDEX IdxIndex ON " << TableName << "(idx ASC)";
                        sqlite3_exec(sqliteDB, (const char*)indexSQL.toAscii(), NULL, NULL, NULL);
                        */

        } else {
            //Some sorta weird error can't create tableName
            std::cout << "Error: Can't create Table: " << TableName << "Sqlite3 error:" << sqlite3_errmsg(sqliteDB) << std::endl;
            return;
        } //end if(error = SQLITE_OK)

        //Okay, table should now be created need to try to assign the list again
        if(!TryAssignList()) {
            //Damn another error what is it this time
            std::cout << "Sqlite3 error: " << sqlite3_errmsg(sqliteDB) << std::endl;
        }

    } //end if(!TryAssignList)

} //End CreateListDB

//Helper to create list DB.  Tries to assign the table a id
//Return false if can't get an id.  Probably because the table doesn't exist
template <class T>
        bool plQList<T>::TryAssignList() {
    //Need to get the database and the id of this list
    plDatabase* database = GetDatabase();
    database->GetData(this);

    sqlite3* sqliteDB = database->GetSqliteDB();

    //Need to get the max id
    std::stringstream maxSQL;
    maxSQL << "SELECT max(ListId) from " << TableName;

    sqlite3_stmt* maxStatement;
    int error = sqlite3_prepare_v2(sqliteDB, maxSQL.str().c_str(), -1, &maxStatement, NULL);
    if(error == SQLITE_OK) {
        //Found the max id for
        sqlite3_step(maxStatement);
        ListId = sqlite3_column_int64(maxStatement, 0);
        ListId++; //Increas it by one, make it unique
        sqlite3_finalize(maxStatement);

        //Set the list id for the owner object
        database->SetData(this);
    } else {
        //Error probably table not created
        if(maxStatement) {
            sqlite3_finalize(maxStatement);
        }
        return false;
    }

    //First try to insert a dumy node
    std::stringstream insertSQL;
    insertSQL << "INSERT INTO " << TableName << " (ListId, Idx) VALUES (" << ListId << ",-1)";

    sqlite3_stmt* statement;
    error = sqlite3_prepare_v2(sqliteDB, insertSQL.str().c_str(), -1, &statement, NULL);
    if(error == SQLITE_OK) {
        //Adds entry to
        sqlite3_step(statement);
        sqlite3_finalize(statement);
    } else {
        sqlite3_finalize(statement);
        std::cout << "Error plQList: Can't insert sql:" << insertSQL << std::endl;
        return false;
    }

    return true;

} //end TryAssignList

#endif //plQList_H

